using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Linq;
using FwkLight.CodeExtensions;
using xVal.ServerSide;

namespace FwkLight.Web.Validation
{
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
  public class ValidateModelAttribute : ActionFilterAttribute
  {
    private readonly Type _viewModelType;

    public ValidateModelAttribute ( Type viewModelType )
    {
      _viewModelType = viewModelType;
    }


    public override void OnActionExecuting ( ActionExecutingContext filterContext )
    {
      List<ErrorInfo> errors = ValidateIncomingModels(filterContext);

      if (errors.Count() > 0)
      {
        AddErrorsToModel(filterContext, errors);
      }
    }

    private void AddErrorsToModel ( ControllerContext filterContext, IEnumerable<ErrorInfo> errors )
    {
      AddModelStateErrors(filterContext.Controller.ViewData.ModelState, "", errors, x => true);
    }

    private List<ErrorInfo> ValidateIncomingModels ( ActionExecutingContext filterContext )
    {
      var parametersToValidate = (from actionParameter in filterContext.ActionParameters
                                  where actionParameter.Value != null && actionParameter.Value.GetType() == _viewModelType
                                  select actionParameter.Value);
      List<ErrorInfo> errors = new List<ErrorInfo>();

      parametersToValidate.ForEach(x => errors.AddRange(DataAnnotationsValidationRunner.GetErrors(x)));
      return errors;
    }

    private void AddModelStateErrors ( ModelStateDictionary modelState, string prefix, IEnumerable<ErrorInfo> errors, Func<ErrorInfo, bool> errorFilter )
    {
      if (errorFilter == null) throw new ArgumentNullException("errorFilter");
      prefix = prefix == null ? "" : prefix + "";
      foreach (var errorInfo in errors.Where(errorFilter))
      {
        var key = prefix + errorInfo.PropertyName;
        modelState.AddModelError(key, errorInfo.ErrorMessage);

        // Workaround for http://xval.codeplex.com/WorkItem/View.aspx?WorkItemId=1297 (ASP.NET MVC bug)
        // Ensure that some value object is registered in ModelState under this key
        ModelState existingModelStateValue;
        if (modelState.TryGetValue(key, out existingModelStateValue) && existingModelStateValue.Value == null)
          existingModelStateValue.Value = new ValueProviderResult(null, null, null);
      }
    }

  }
}